﻿import type { RequestOptions } from '@@/plugin-request/request';
import type { RequestConfig } from '@umijs/max';
import { history, request } from '@umijs/max';
import { message, notification } from 'antd';
import { isString } from 'lodash';
import qs from 'qs';
import { checkTokenTime } from './utils';

// 错误处理方案： 错误类型
enum ErrorShowType {
  SILENT = 0,
  WARN_MESSAGE = 1,
  ERROR_MESSAGE = 2,
  NOTIFICATION = 3,
  REDIRECT = 9,
}
// 与后端约定的响应数据格式
interface ResponseStructure {
  success: boolean;
  data: any;
  errorCode?: number;
  code?: number | string;
  errorMessage?: string;
  showType?: ErrorShowType;
}
const getTokenItme = (param: string): string => {
  const blogTokenObj = localStorage.getItem(BASE_TOKEN_NAME) || '{}';
  return JSON.parse(blogTokenObj)[param];
};
// 当前页面是否不需要登录
const isPass = () => {
  return WHITE_LIST.some((item: string) => {
    return window.location.pathname.startsWith(item) || window.location.pathname === '/';
  });
};

// 是否正在刷新的标记 -- 防止重复发出刷新token接口
let isRefreshing = false;
// 失效后同时发送请求的容器 -- 缓存接口
let subscribers: any[] = [];
// 刷新 token 后, 将缓存的接口重新请求一次
function onAccessTokenFetched(newToken: string) {
  subscribers.forEach((callback) => {
    callback(newToken);
  });
  // 清空缓存接口
  subscribers = [];
}
// 添加缓存接口
function addSubscriber(callback: (newToken: string) => void) {
  subscribers.push(callback);
}
const getNewToken = (refreshToken?: string) => {
  const param = {
    grant_type: 'refresh_token',
    refresh_token: getTokenItme('refresh_token'),
    client_id: 'admin',
    client_secret: '112233',
  };
  if (refreshToken) {
    param.refresh_token = refreshToken;
  }
  console.log(999);

  return request<COMMON.BaseSuccess<COMMON.TokenType>>('/api/authorization/token/refresh', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    data: qs.stringify(param),
  });
};

/**
 * 刷新令牌
 * @param token 令牌对象
 */
async function refreshingToken(token?: string) {
  console.log(isRefreshing);

  if (!isRefreshing) {
    isRefreshing = true;
    return new Promise((resolve) => {
      // 请求刷新令牌
      getNewToken(token)
        .then((res: any) => {
          console.log(res);

          res.tokenSurvivalTime = Date.now() + res.expires_in * 1000;
          localStorage.setItem(BASE_TOKEN_NAME, JSON.stringify(res));
          resolve(res);
        })
        .finally(() => {
          // 恢复变量值
          isRefreshing = false;
        });
    });
  } else {
    // 阻塞当前请求
    return new Promise((resolve) => {
      const id = setInterval(() => {
        console.log('请求被阻塞了');
        if (!isRefreshing) {
          clearTimeout(id);
          resolve(1);
        }
      }, 1000);
    });
  }
}

/**
 * @name 错误处理
 * pro 自带的错误处理， 可以在这里做自己的改动
 * @doc https://umijs.org/docs/max/request#配置
 */
export const errorConfig: RequestConfig = {
  // 错误处理： umi@3 的错误处理方案。
  errorConfig: {
    // 错误抛出
    errorThrower: (res) => {
      const { success, data, errorCode, errorMessage, showType } =
        res as unknown as ResponseStructure;
      if (!success) {
        const error: any = new Error(errorMessage);
        error.name = 'BizError';
        error.info = { errorCode, errorMessage, showType, data };
        throw error; // 抛出自制的错误
      }
    },
    // 错误接收及处理
    errorHandler: (error: any, opts: any) => {
      const { response: { status = 500, config: { url = '' } = {} } = {}, response } = error;
      if (url.includes('/authorization/token/refresh')) {
        // 刷新token报错的话, 就需要跳转到登录页面  删除过期token和登录人信息
        isRefreshing = false;
        localStorage.removeItem(BASE_TOKEN_NAME);
        localStorage.removeItem('blogUserInfo');
        if (!isPass()) {
          history.replace('/plaza');
        }
      }
      /**
       * /security/oauth/token为刷新token的接口, 需要排除掉
       */
      if (status === 401 && !url?.includes('/authorization/token/refresh')) {
        if (!isRefreshing && localStorage.getItem(BASE_TOKEN_NAME)) {
          // 如果本地存在token并且是未在刷新token
          isRefreshing = true;
          getNewToken().then((res: any) => {
            res.tokenSurvivalTime = Date.now() + res.expires_in * 1000;
            localStorage.setItem(BASE_TOKEN_NAME, JSON.stringify(res));
            onAccessTokenFetched(res.access_token);
          });
        } else {
          // 删除过期token和登录人信息
          localStorage.removeItem(BASE_TOKEN_NAME);
          localStorage.removeItem('blogUserInfo');
          if (!isPass()) {
            history.replace('/plaza');
          }
        }
        // 将其他接口缓存起来 -- 这个Promise函数很关键
        new Promise((resolve) => {
          // 这里是将其他接口缓存起来的关键, 返回Promise并且让其状态一直为等待状态,
          // 只有当token刷新成功后, 就会调用通过addSubscriber函数添加的缓存接口,
          // 此时, Promise的状态就会变成resolve
          addSubscriber((newToken) => {
            // 表示用新的token去替换掉原来的token
            if (response.headers) {
              response.headers.Authorization = `bearer ${newToken}`;
            }
            request(response.config.url, { ...response.config }).then((res) => {
              // 用重新封装的config去请求, 就会将重新请求后的返回
              resolve(res);
            });
          });
        });
      } else {
        if (response?.data?.message) message.error(response.data.message);
        if (opts?.skipErrorHandler) throw error;
      }
      // 我们的 errorThrower 抛出的错误。
      if (error.name === 'BizError') {
        const errorInfo: ResponseStructure | undefined = error.info;
        if (errorInfo) {
          const { errorMessage, errorCode } = errorInfo;
          switch (errorInfo.showType) {
            case ErrorShowType.SILENT:
              // do nothing
              break;
            case ErrorShowType.WARN_MESSAGE:
              message.warn(errorMessage);
              break;
            case ErrorShowType.ERROR_MESSAGE:
              message.error(errorMessage);
              break;
            case ErrorShowType.NOTIFICATION:
              notification.open({
                description: errorMessage,
                message: errorCode,
              });
              break;
            case ErrorShowType.REDIRECT:
              // TODO: redirect
              break;
            default:
              message.error(errorMessage);
          }
        }
      } else if (error.response) {
        // Axios 的错误
        // 请求成功发出且服务器也响应了状态码，但状态代码超出了 2xx 的范围
        // message.error(`Response status:${error.response.status}`);
      } else if (error.request) {
        // 请求已经成功发起，但没有收到响应
        // \`error.request\` 在浏览器中是 XMLHttpRequest 的实例，
        // 而在node.js中是 http.ClientRequest 的实例
        message.error('None response! Please retry.');
      } else {
        // 发送请求时出了点问题
        message.error('Request error, please retry.');
      }
    },
  },
  // 请求拦截器
  requestInterceptors: [
    async (config: RequestOptions) => {
      const tokenString = localStorage.getItem(BASE_TOKEN_NAME);
      if (config.url === '/api/authorization/token/refresh') {
        config.Auth = {
          Username: 'admin',
          Password: '112233',
        };
      }
      const flag = checkTokenTime();
      if (tokenString) {
        // 当前token已经过期 去尝试置换新的token
        if (flag) {
          refreshingToken(tokenString);
        }
      } else {
        // 未登录并且不在白名单
        !isPass() && history.replace('/login');
      }
      const tokenObj = localStorage.getItem(BASE_TOKEN_NAME);
      if (config.headers && tokenObj) {
        const { access_token, token_type = 'bearer' } = JSON.parse(tokenObj);
        config.headers.Authorization = `${token_type} ` + access_token;
      }
      return config;
    },
  ],

  // 响应拦截器
  responseInterceptors: [
    (response: any) => {
      const { data: { code = 200 } = {} } = response;
      /**
       * /security/oauth/token为刷新token的接口, 需要排除掉
       */
      if (code === 401) {
        if (!isRefreshing && localStorage.getItem(BASE_TOKEN_NAME)) {
          // 如果本地存在token并且是未在刷新token
          isRefreshing = true;
          getNewToken().then((res: any) => {
            localStorage.setItem(BASE_TOKEN_NAME, JSON.stringify(res));
            onAccessTokenFetched(res.access_token);
          });
        } else {
          // 删除过期token和登录人信息
          localStorage.removeItem(BASE_TOKEN_NAME);
          localStorage.removeItem('blogUserInfo');
          if (!isPass()) {
            // 如果不在白名单里 得到了401请求直接踢到广场页面
            history.replace('/plaza');
          }
        }
        // 将其他接口缓存起来 -- 这个Promise函数很关键
        new Promise((resolve) => {
          // 这里是将其他接口缓存起来的关键, 返回Promise并且让其状态一直为等待状态,
          // 只有当token刷新成功后, 就会调用通过addSubscriber函数添加的缓存接口,
          // 此时, Promise的状态就会变成resolve
          addSubscriber((newToken) => {
            // 表示用新的token去替换掉原来的token
            if (response.headers) {
              response.headers.Authorization = `bearer ${newToken}`;
            }
            request(response.config.url, { ...response.config }).then((res) => {
              // 用重新封装的config去请求, 就会将重新请求后的返回
              resolve(res);
            });
          });
        });
      }
      // 拦截响应数据，进行个性化处理
      const { data } = response as unknown as ResponseStructure;
      if (data?.success === false) {
        message.error('请求失败！');
      }
      if (isString(data)) {
        // 因为默认ahooks会把data剥一层 如果第一层data直接是一个string 剥一层就是undefined了
        response.data = { data };
      }
      return response;
    },
  ],
};
